/*
 * @Author: shijianxin
 * @Date: 2022-04-11 09:29:21
 * @Description: 方块移动规则类
 */

import GameConfig from "./GameConfig";
import { Square } from "./Square";
import { SquareGroup } from "./SquareGroup";
import { MoveDirection, Point, Shape } from "./types";

/**
 * 判断是否有坐标
 * @param obj
 * @returns
 */
function isPoint(obj: any): obj is Point {
  if (typeof obj.x === "undefined") {
    return false;
  }
  return true;
}
export class TerisRule {
  /**
   * 判断是否可以移动到目标位置
   */
  static canIMove(shape: Shape, targetPoint: Point, exists: Square[]): boolean {
    // 预计的目标坐标
    const targetSquarePoints: Point[] = shape.map((it) => {
      return {
        x: it.x + targetPoint.x,
        y: it.y + targetPoint.y,
      };
    });
    // 判断预计的目标坐标是否有某个坐标超出了范围（边界判断）
    let result = targetSquarePoints.some((p) => {
      return (
        p.x < 0 ||
        p.x > GameConfig.panelSize.width - 1 ||
        p.y < 0 ||
        p.y > GameConfig.panelSize.height - 1
      );
    });
    if (result) {
      return false;
    }
    // 判断是否与已有的方块重叠
    result = targetSquarePoints.some((p) =>
      exists.some((sq) => sq.point.x === p.x && sq.point.y === p.y)
    );
    if (result) {
      return false;
    }
    return true;
  }

  static move(
    teris: SquareGroup,
    targetPoint: Point,
    exists: Square[]
  ): boolean;
  static move(
    teris: SquareGroup,
    direction: MoveDirection,
    exists: Square[]
  ): boolean;
  /**
   * 小方块移动
   * @param teris 小方块的形状坐标
   * @param targetPointOrDirection 小方块新的中心坐标 或 方向
   * @returns
   */
  static move(
    teris: SquareGroup,
    targetPointOrDirection: Point | MoveDirection,
    exists: Square[]
  ) {
    // 判断传入的是坐标还是方向
    // 如果是坐标
    if (isPoint(targetPointOrDirection)) {
      // 判断是否可移动
      if (this.canIMove(teris.shape, targetPointOrDirection, exists)) {
        // 设置新的中心坐标
        teris.centerPoint = targetPointOrDirection;
        return true;
      }
      return false;
    } else {
      // 如果是方向，则先计算出相对应方向移动后的坐标
      const direction = targetPointOrDirection;
      let targetPoint: Point;
      if (direction === MoveDirection.down) {
        targetPoint = {
          x: teris.centerPoint.x,
          y: teris.centerPoint.y + 1,
        };
      } else if (direction === MoveDirection.left) {
        targetPoint = {
          x: teris.centerPoint.x - 1,
          y: teris.centerPoint.y,
        };
      } else {
        targetPoint = {
          x: teris.centerPoint.x + 1,
          y: teris.centerPoint.y,
        };
      }
      // 计算出坐标后，重新调用该方法
      return this.move(teris, targetPoint, exists);
    }
  }
  /**
   * 小方块朝某个方向持续移动
   * @param teris
   * @param direction
   */
  static moveDirectly(
    teris: SquareGroup,
    direction: MoveDirection,
    exists: Square[]
  ) {
    while (this.move(teris, direction, exists)) {}
  }

  /**
   * 判断旋转之后是否超出范围，没有的话则可以旋转，直接旋转
   * @param teris
   * @returns
   */
  static rotate(teris: SquareGroup, exists: Square[]): boolean {
    const newShape = teris.afterRotateShape(); // 得到旋转之后的坐标
    if (this.canIMove(newShape, teris.centerPoint, exists)) {
      teris.rotate();
      return true;
    } else {
      return false;
    }
  }

  /**
   * 从已存在的方块中进行消除，并返回消除的行数
   * @param exists
   */
  static deleteSquare(exists: Square[]): number {
    // 获取y坐标数组
    const ys = exists.map((sq) => sq.point.y);
    // 获取最大和最小的y坐标
    const maxY = Math.max(...ys);
    const minY = Math.min(...ys);
    // 循环判断每一行是否可以消除
    let num = 0;
    for (let y = minY; y <= maxY; y++) {
      if (this.deleteLine(exists, y)) {
        num++;
      }
    }
    return num;
  }

  /**
   * 消除一行
   * @param exists
   * @param y
   */
  private static deleteLine(exists: Square[], y: number): boolean {
    const squares = exists.filter((sq) => sq.point.y === y);
    if (squares.length === GameConfig.panelSize.width) {
      squares.forEach((sq) => {
        // 从界面中移除
        if (sq.viewer) {
          sq.viewer.remove();
        }
        // 从数组中移除
        const index = exists.indexOf(sq);
        exists.splice(index, 1);
      });
      // 剩下的，y坐标比当前y小的方块，y+1
      exists
        .filter((sq) => sq.point.y < y)
        .forEach((sq) => {
          sq.point = {
            x: sq.point.x,
            y: sq.point.y + 1,
          };
        });
      return true;
    }
    return false;
  }
}
